package com.wan.data.mysql.orm.repository;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.wan.Kit;
import com.wan.LifeCycle;
import com.wan.core.timer.WTimer;
import com.wan.core.timer.task.TaskNode;
import com.wan.data.mysql.DbConstant;
import com.wan.data.mysql.orm.entity.Entity;
import com.wan.setting.Conf;
import com.wan.thread.ExecutorManager;
import com.wan.util.ObjectUtil;
import com.wan.util.number.LongRange;

import java.io.Serializable;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;

/**
 * 具有定时延迟入库特性的数据仓库
 * 只重写了save方法
 */
public class DelayRepository<PK extends Serializable,E extends Entity<PK>> extends EntityRepository<PK,E> implements LifeCycle {
    private static final Log log = LogFactory.get();

    private final TaskNode delayTask;
    private final Map<Object,E> delayMap = new ConcurrentHashMap<>();

    public DelayRepository() {
        super();
        LongRange delayRange = getDelayRange();
        //范围内随机一个值，防止不同仓库数据大批量同时入库，减少数据库压力
        long delay = delayRange.randomNext();
        delayTask = WTimer.get().repeatLimit(node -> flush(false),
                Duration.ofMillis(delay), -1, false);
    }

    @Override
    public E get(PK pk) {
        E e = delayMap.get(pk);
        if(e != null){
            return e;
        }
        return super.get(pk);
    }

    @Override
    public Collection<E> get(Collection<PK> pks) {
        List<E> list = new ArrayList<>();
        Iterator<PK> iterator = pks.iterator();
        while (iterator.hasNext()){
            PK next = iterator.next();
            E e = delayMap.get(next);
            if(e != null){
                iterator.remove();
                list.add(e);
            }
        }
        if(!pks.isEmpty()){
            list.addAll(super.get(pks));
        }
        return list;
    }

    @Override
    public void delete(Collection<PK> list) {
        for (PK pk : list) {
            delayMap.remove(pk);
        }
        super.delete(list);
    }

    /**
     * 延迟保存
     */
    @Override
    public void save(Collection<E> list){
        save(list,true);
    }

    public void save(Collection<E> list,boolean delay){
        if(delay){
            for (E e : list) {
                save(e);
            }
        }else{
            super.save(list);
        }
    }

    /**
     * 延迟保存,自增值无法及时获得
     */
    @Override
    public void save(E e){
        save(e,true);
    }

    public void save(E e,boolean delay){
        if(delay){
            Object pk = e.getPk();
            if(ObjectUtil.isEmptyOrZero(pk)){
                pk = new Object();
            }
            delayMap.put(pk,e);
        }else{
            super.save(e);
        }
    }

    /**
     * 数据入库
     */
    protected void flush(boolean sync){
        if(delayMap.isEmpty()){
            return;
        }
        Iterator<E> iterator = delayMap.values().iterator();
        List<E> saveList = new ArrayList<>();
        while (iterator.hasNext()){
            E next = iterator.next();
            saveList.add(next);
            iterator.remove();
        }
        //一定要使用父类的save方法
        if(sync){
            //同步执行
            super.save(saveList);
        }else{
            //异步执行
            ExecutorService executor = ExecutorManager.getExecutor("data-save", ExecutorManager.DEFAULT_CONFIG);
            executor.execute(()->super.save(saveList));
        }
    }

    /**
     * 指定延迟范围(毫秒)
     */
    private LongRange getDelayRange(){
        //限制最小2000ms
        long delay = Math.max(2000L,getDelay());
        //取配置的值范围 value * 0.75  -> value * 1.5,例如配5000ms 范围为[3750,7500];
        return LongRange.of((long) (delay * 0.75), (long) (delay * 1.5));
    }

    /**
     * 单独仓库想自定义延迟可重写
     */
    protected long getDelay(){
        return Conf.get().getLong(DbConstant.ConfVar.DB_DELAY, Kit.mysql.name(), 7000L);
    }

    @Override
    public void destroy() throws Exception {
        delayTask.cancel();
        if(!delayMap.isEmpty()){
            log.info("stopping -> 延迟仓库数据立即入库--> size[{}][{}]",delayMap.size(),tableMeta.getEntityType().getCanonicalName());
            flush(true);
        }
    }
}
